package com.ruoyi.system.ms.dataAuth.core;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.mashuai.utils.base.MsIocUtils;
import com.mashuai.utils.toolkit.core.msConverts.MsStreamUtils;
import com.ruoyi.system.ms.dataAuth.annations.MsDataAuth;
import com.ruoyi.system.ms.dataAuth.annations.MsDataAuthScans;
import com.ruoyi.system.ms.dataAuth.dto.DataAuthAnalysisEntityDto;
import com.ruoyi.system.ms.dataAuth.dto.DataAuthAnalysisMapperDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * springboot启动结束后，扫描指定包下所有@MsDataAuth注解相关信息
 *
 * @Author: 马帅
 **/
@Slf4j
@Component
public class DataAuthScans implements ApplicationRunner {
    //缓存全局可用
    //key=mapper类名+方法名。val=所包含的方法
    public static List<DataAuthAnalysisMapperDto> dataAuthMapperCacheList = new CopyOnWriteArrayList<>();
    public static Map<String, List<DataAuthAnalysisMapperDto>> dataAuthMapperCacheMap = new ConcurrentHashMap<>();
    //key=表名，val=实体类信息
    public static List<DataAuthAnalysisEntityDto> dataAuthEntityCacheList = new CopyOnWriteArrayList<>();
    public static Map<String, List<DataAuthAnalysisEntityDto>> dataAuthEntityCacheMap = new ConcurrentHashMap<>();

    //资源加载器
    private ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
    private final ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
    private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    //常量-mp得BaseMapper类路径
    private final static String BASE_MAPPER_PATH = "com.baomidou.mybatisplus.core.mapper.BaseMapper";
    //解析实体类需要排除的字段名称
    private final static String EXCLUDE_FIELD_NAMES = "serialVersionUID";

    @Override
    public void run(ApplicationArguments args) {
        long beginTime = System.currentTimeMillis();
        //获取启动类上的@MsDataAuthScans注解，理论上一个项目启动类只可能有一个
        Map<String, Object> annotation = MsIocUtils.getBeansWithAnnotation(SpringBootApplication.class);
        if (annotation.size() != 0) {
            for (String key : annotation.keySet()) {
                Class<?> clz = annotation.get(key).getClass();
                MsDataAuthScans scansA = MsIocUtils.findAnnotation(clz, MsDataAuthScans.class);
                if (scansA != null) {
                    String[] entityPackages = scansA.scanEntityPackages();
                    doScanClz(entityPackages, doClz -> {
                        doAnalysisEntity(doClz);
                    });
                    String[] mapperPackages = scansA.scanMapperPackages();
                    doScanClz(mapperPackages, doClz -> {
                        if (checkBaseMapper(doClz)) {
                            doAnalysisMapper(doClz);
                        }
                    });
                }
            }
        }
        log.info("扫描所有@MsDataAuth注解耗时：{}/ms", (System.currentTimeMillis() - beginTime));
        //缓存所有已经配置好的数据信息
    }


    /**
     * 通用包扫描，会返回其包下每一个class类，交给业务进行处理
     **/
    private void doScanClz(String[] packages, IDataAuthScan iScan) {
        try {
            for (String path : packages) {
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX.concat(ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(path)).concat("/**/*.class"));
                Resource[] resources = resolver.getResources(packageSearchPath);
                MetadataReader metadataReader = null;
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        metadataReader = metadataReaderFactory.getMetadataReader(resource);
                        try {
                            Class<?> clz = Class.forName(metadataReader.getClassMetadata().getClassName());
                            iScan.handleClass(clz);
                        } catch (Exception e) {
                            log.info("在进行@MsEntityScans包扫描时，class类解析错误:" + e.getMessage());
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 检查Mapper接口是否继承BaseMapper
     *
     * @param clz
     * @return Boolean true继承
     * @Author: 马帅
     **/
    private Boolean checkBaseMapper(Class<?> clz) {
        Class<?>[] interfaces = clz.getInterfaces();
        if (interfaces != null && interfaces.length != 0) {
            for (Class<?> ic : interfaces) {
                String name = ic.getName();
                if (BASE_MAPPER_PATH.equals(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 扫描继承BaseMapper的Mapper接口
     *
     * @param clz
     * @return void
     * @Author: 马帅
     **/
    private void doAnalysisMapper(Class<?> clz) {
        String clzName = clz.getName();
        for (Method method : clz.getDeclaredMethods()) {
            MsDataAuth authA = method.getAnnotation(MsDataAuth.class);
            if (authA != null) {
                String methodName = method.getName();
                String tableName = authA.defTableName();
                DataAuthAnalysisMapperDto dto = new DataAuthAnalysisMapperDto();
                List<DataAuthAnalysisEntityDto> entityDtoList = dataAuthEntityCacheMap.get(tableName);
                if (entityDtoList != null) {
                    dto.setTableName(tableName);
                    //按照规范来定义，每张表只会存在一个实体类
                    dto.setSubClzList(entityDtoList.get(0).getSubClzList());
                }
                dto.setDescription(authA.description());
                dto.setPathName(clzName + "." + methodName);
                dataAuthMapperCacheList.add(dto);
            }
        }
        dataAuthMapperCacheMap = MsStreamUtils.groupBy(dataAuthMapperCacheList, DataAuthAnalysisMapperDto::getPathName);
    }

    /**
     * 解析实体类
     *
     * @param doClz
     * @Author: 马帅
     **/
    private void doAnalysisEntity(Class<?> doClz) {
        TableName tbA = doClz.getAnnotation(TableName.class);
        if (tbA != null) {
            DataAuthAnalysisEntityDto dto = new DataAuthAnalysisEntityDto();
            dto.setClzName(doClz.getName());
            dto.setTableName(tbA.value());
            List<DataAuthAnalysisEntityDto.SubClz> subClzList = new ArrayList<>();
            setDefFiled(subClzList);
            dto.setSubClzList(subClzList);
            for (Field field : doClz.getDeclaredFields()) {
                String javaField = field.getName();
                if (EXCLUDE_FIELD_NAMES.contains(javaField)) continue;
                String sqlFiled = field.getName();
                field.setAccessible(true);
                TableField fieldA = field.getAnnotation(TableField.class);
                boolean exist = true;//默认字段是存于表
                if (fieldA != null) {
                    sqlFiled = fieldA.value();
                    exist = fieldA.exist();
                }
                if (exist) {
                    subClzList.add(new DataAuthAnalysisEntityDto.SubClz(javaField, sqlFiled));
                }

            }
            dataAuthEntityCacheList.add(dto);
            dataAuthEntityCacheMap = MsStreamUtils.groupBy(dataAuthEntityCacheList, DataAuthAnalysisEntityDto::getTableName);
        }
    }

    /**
     * 设置父亲的基础字段
     *
     * @param subClzList
     * @Author: 马帅
     **/
    private void setDefFiled(List<DataAuthAnalysisEntityDto.SubClz> subClzList) {
        subClzList.add(new DataAuthAnalysisEntityDto.SubClz("createId", "create_id"));
        subClzList.add(new DataAuthAnalysisEntityDto.SubClz("createTime", "create_time"));
        subClzList.add(new DataAuthAnalysisEntityDto.SubClz("updateId", "update_id"));
        subClzList.add(new DataAuthAnalysisEntityDto.SubClz("updateTime", "update_time"));
        subClzList.add(new DataAuthAnalysisEntityDto.SubClz("remark", "remark"));
    }

}
